package fr.cephb.joperon.tool.stypesgui;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.SAXException;

import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.Environment;
import com.sleepycat.db.EnvironmentConfig;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.Transaction;

import fr.cephb.berkeley.baseapi.db.PrimaryDB;
import fr.cephb.berkeley.baseapi.db.Walker;
import fr.cephb.joperon.core.types.Fid;
import fr.cephb.joperon.core.types.bindings.FidBinding;
import fr.cephb.joperon.structures.GTypes;
import fr.cephb.joperon.structures.STypes;
import fr.cephb.joperon.structures.bindings.GTypesArrayBinding;
import fr.cephb.joperon.structures.bindings.STypesArrayBinding;


/**
 * 
 * Model
 *
 */
public class Model
	{
	private static final Logger LOG=Logger.getLogger("fr.cephb.joperon");
	//public static final int NUM_SAMPLES=13;
	
	
	@XmlRootElement(name="config")
	public static class Config
		{
		@XmlElement(required=true,name="db-home")
		public String dbHome=null;
		@XmlElement(required=true,name="url-pattern")
		public String urlPattern=null;
		@XmlElement(required=true,name="snpmap")
		public String snpmap=null;
		@XmlElement(required=true,name="samples-count")
		int samples=-1;
		@XmlElementWrapper(name="stypes")
		@XmlElement(name="path")
		public List<String> input=new ArrayList<String>();
		
		String gtypesdb=null;//TODO
		}
	

	
	
	/**
	 * 
	 * ChromInfo
	 *
	 */
	public static class ChromInfo
	implements Cloneable,Comparable<ChromInfo>
		{
		private int chrom_start=0;
		private int chrom_length=0;
		private int chrom_id=1;
		@Override
		public int compareTo(ChromInfo o)
			{
			return this.chrom_id-o.chrom_id;
			}
		@Override
		protected Object clone()
			{
			ChromInfo cp = new ChromInfo();
			cp.chrom_id=cp.chrom_id;
			cp.chrom_length=cp.chrom_length;
			cp.chrom_start=cp.chrom_start;
			return cp;
			}
		@Override
		public String toString()
			{
			return "chr"+this.chrom_id;
			}
		
		public int getId()
			{
			return chrom_id;
			}
		
		public int getLength()
			{
			return chrom_length;
			}
		
		public int getStart()
			{
			return chrom_start;
			}
		public int getDataSize()
			{
			return this.getLength()-getStart();
			}
		}

//private File dbHome=new File(System.getProperty("java.io.tmpdir"),"bdb");
//public static final String DEFAULT_FILE="dbs/dna/hsa/36/ref/stypes/Cancer_D1_5.bdb";
private Environment environment=null;

private Transaction txn=null;
private Map<Integer,ChromInfo> id2chromInfo=new TreeMap<Integer, ChromInfo>();
//private String dbFile=DEFAULT_FILE;
private Config config=null;


/** create an unmarshaller for config.xsd for the given class */
public static Unmarshaller createUnmarshaller(Class<?> clazz)
	throws SAXException,IOException,JAXBException
	{
	LOG.info("loading schema");
	final String schemaName="config.xsd";
	SchemaFactory factory=SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
	
	InputStream schemaIn= Model.class.getResourceAsStream(schemaName);
	if(schemaIn==null) throw new IOException("Cannot get schema "+schemaName);
	Schema schema=factory.newSchema(new StreamSource(schemaIn));
	schemaIn.close();
	
	JAXBContext context = JAXBContext.newInstance(clazz);
	Unmarshaller unmarshaller=context.createUnmarshaller();
	unmarshaller.setSchema(schema);
	return unmarshaller;
	}


/**
 * open
 * @param dbHome
 * @throws DatabaseException
 * @throws FileNotFoundException
 */
public void open(File configFile) throws DatabaseException,FileNotFoundException
	{
	close();
	
	try {
		this.config= (Config)(createUnmarshaller(Config.class).unmarshal(configFile));
		}
	catch(Exception e)
		{
		throw new RuntimeException(e);
		}

	
	EnvironmentConfig envConfig= new EnvironmentConfig();
	envConfig.setAllowCreate(true);
	LOG.info("opening environment "+this.config.dbHome);
	this.environment= new Environment(new File(this.config.dbHome), envConfig);
	
	
	for(String path: config.input)
		{
		PrimaryDB<Fid, STypes[]> stypesdb=openSTypesDB(path);
		Walker<Fid,STypes[]> w= stypesdb.openWalker(txn);
		boolean first=true;
		while(true)
			{
			if(first)
				{
				first=false;
				if(w.getLast()!=OperationStatus.SUCCESS) throw new DatabaseException("cannot get last");
				}
			else
				{
				if(w.getPrev()!=OperationStatus.SUCCESS) break;
				}
			Fid pos= w.getKey();
			ChromInfo info= new ChromInfo();
			info.chrom_id=pos.getChromosomeId();
			info.chrom_length=pos.getPosition()+1;
			if(w.getSearchKeyRange(new Fid(pos.getOrganismId(),info.chrom_id, 0))!=OperationStatus.SUCCESS)
				{
				throw new DatabaseException("cannot find lower bound");
				}
			if(w.getKey().getChromosomeId()!=info.chrom_id) throw new DatabaseException("not same chr ??");
			info.chrom_start=w.getKey().getPosition();
			//System.err.println("chr"+info.chrom_id+" start:"+info.getStart()+" length="+info.getLength());
			if(this.id2chromInfo.containsKey(info.chrom_id))  throw new DatabaseException("duplicate chr ??");
			this.id2chromInfo.put(info.chrom_id, info);
			}
		w.close();
		stypesdb.close();
		}
	
	}
public Environment getEnvironment()
	{
	return environment;
	}

public String getSnpMapPath()
	{
	return config.snpmap;
	}

public List<String> getSTypes()
	{
	return config.input;
	}

public String getGTypes()
	{
	return config.gtypesdb;//TODO
	}

public List<ChromInfo> getChromosomes()
	{
	return new ArrayList<ChromInfo>(id2chromInfo.values());
	}

public PrimaryDB<Fid, STypes[]> openSTypesDB(String path) throws DatabaseException,FileNotFoundException
	{
	PrimaryDB<Fid, STypes[]> pos2genotypes;
	
	DatabaseConfig cfg= new DatabaseConfig();
	cfg.setAllowCreate(false);
	cfg.setReadOnly(true);
	try
		{
		pos2genotypes= new PrimaryDB<Fid,STypes[]>(
				this.environment, txn,path ,null,
				cfg,
				new FidBinding(), 
				new STypesArrayBinding(config.samples)
				);
		return pos2genotypes;
		}
	catch(FileNotFoundException err)
		{
		throw new FileNotFoundException(err.getMessage()+" "+path);
		}
	

	}


public PrimaryDB<Fid, GTypes[]> openGTypesDB(String path) throws DatabaseException,FileNotFoundException
	{
	PrimaryDB<Fid, GTypes[]> pos2genotypes;
	
	DatabaseConfig cfg= new DatabaseConfig();
	cfg.setAllowCreate(false);
	cfg.setReadOnly(true);
	try
		{
		pos2genotypes= new PrimaryDB<Fid,GTypes[]>(
				this.environment, txn,path ,null,
				cfg,
				new FidBinding(), 
				new GTypesArrayBinding(config.samples)//TODO
				);
		return pos2genotypes;
		}
	catch(FileNotFoundException err)
		{
		throw new FileNotFoundException(err.getMessage()+" "+path);
		}
	}


public ChromInfo getChromById(Integer chromId)
	{
	return this.id2chromInfo.get(chromId);
	}

public int getNumberOfSamples()
	{
	return this.config.samples;
	}


public void close() throws DatabaseException
	{
	if(this.environment!=null) this.environment.close();
	}


}
